Skip to content

Delegate conditional-decode 412 decision to prefix-based-pd-decider#1684

Open
dmitripikus wants to merge 8 commits into
llm-d:mainfrom
dmitripikus:cond-decode-config
Open

Delegate conditional-decode 412 decision to prefix-based-pd-decider#1684
dmitripikus wants to merge 8 commits into
llm-d:mainfrom
dmitripikus:cond-decode-config

Conversation

@dmitripikus

@dmitripikus dmitripikus commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind feature

What this PR does / why we need it:
Move the RFC 7240 Prefer: if-available 412 gate out of the director and into
the prefix-based-pd-decider plugin. One nonCachedTokens threshold now drives
both decisions:
disaggregated P/D and conditional-decode rejection.

The director had simplistic and non-configurable implementation of decode condition.

Behavior change: the 412 gate now rejects when the non-cached suffix is at or above nonCachedTokens (rather than rejecting only on a complete cache miss), and nonCachedTokens: 0 disables the gate entirely. Deployments wanting the 412 gate must include - type: prefix-based-pd-decider with a non-zero nonCachedTokens in their EPP config; deployments using the canonical pd-epp-config.yaml already declare the plugin and pick up the gate automatically. See docs/disaggregation.md for details.

Which issue(s) this PR fixes:

1686

Changes

  • New requestcontrol.ConditionalDecodeDecider interface; auto-registered
    through Config.AddPlugins.
  • *PrefixBasedPDDecider implements it via ShouldRejectConditionalDecode,
    sharing computeNonCachedTokens with the existing disaggregate path
    (different fail-mode semantics: 412 fail-closed, disaggregation fail-open).
  • Director consults the configured decider

Release note

    plugins:
     ...
     - type: prefix-based-pd-decider
      parameters:
        nonCachedTokens: <enter tokens number here>
    ...
    
 should be added to decoder configuration map to support conditional decode   

Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
@dmitripikus
dmitripikus requested a review from a team as a code owner June 18, 2026 11:29
@dmitripikus
dmitripikus requested review from ahg-g and liu-cong June 18, 2026 11:29
@github-actions github-actions Bot added kind/feature Categorizes issue or PR as related to a new feature. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jun 18, 2026
@roytman

roytman commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Please open an issue and add it to the PR,

Behavior change in the 412 gate needs to be called out explicitly. This is the substantive change, and it is easy to miss:

  • Old gate (primaryEndpointHasCachedPrefix): forwarded if the endpoint had any matching prefix block (MatchBlocks() > 0); rejected only on a complete cache miss.
  • New gate: rejects when the non-cached suffix is >= nonCachedTokens, and when nonCachedTokens == 0 the gate is disabled entirely (always forwards).

We need to update the README file about the conditional decode behaviour.

@github-actions github-actions Bot added kind/feature Categorizes issue or PR as related to a new feature. and removed kind/feature Categorizes issue or PR as related to a new feature. labels Jun 18, 2026
…f/else"

Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
@github-actions github-actions Bot added kind/feature Categorizes issue or PR as related to a new feature. and removed kind/feature Categorizes issue or PR as related to a new feature. labels Jun 18, 2026
Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
Comment thread pkg/epp/requestcontrol/director.go Outdated
// a real cache miss.
func primaryEndpointHasCachedPrefix(logger logr.Logger, result *fwksched.SchedulingResult) bool {
// primaryDecodeEndpoint returns the first endpoint chosen by the primary
// profile, or nil when the result is empty or malformed. False-return reasons

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the comment is stale.
The function no longer inspects the cache, it only returns the endpoint. So, the "cache miss" framing is now inaccurate.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, Thank you!

Comment thread pkg/epp/requestcontrol/director.go Outdated
Code: errcommon.PreconditionFailed,
Msg: "no decode worker has the requested KV cache",
decider := d.requestControlPlugins.ConditionalDecodeDecider()
switch {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if decider == nil {
    logger.V(logutil.DEBUG).Info("conditional-decode: no decider configured, forwarding")
} else {
    endpoint := primaryDecodeEndpoint(logger, result)
    if endpoint == nil || decider.ShouldRejectConditionalDecode(ctx, reqCtx.SchedulingRequest, endpoint) {
        ...
    }
    ...
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've resolved this when taking care of lint problem.
Thanks!

…hange

Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
@dmitripikus

Copy link
Copy Markdown
Contributor Author

@roytman , addressing your fist set of comments:

  • issue is created and associated with PR
  • behavior change is clarified in "README.md" you mentioned, and also in "disaggregation.md" and in PR description.
    Thanks!

Comment thread docs/disaggregation.md Outdated
- Plugin not declared in the config → gate disabled (the EPP forwards conditional-decode requests unconditionally).
- Prefix-cache state unreadable on the chosen endpoint (e.g. no approximate-prefix producer wired up) → fail closed (412).

This behavior differs from earlier releases, where the gate was a hard-coded check inside the director that forwarded on any cache hit and rejected only on a complete cache miss. The threshold is now in tokens (not blocks), is operator-configurable, and is opt-in via the plugin.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid temporal language as instructed in AGENTS.md.

Would be interesting to understand if your agent "willingly" disobeyed that instruction or it just lost it in context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vMaroon , this is an interesting point.
In this case the agent didn't work according to the instruction.
The agent states that "it didn't deliberately set the rule aside, but it also didn't consciously check it against the wording I was producing..."
We probably need to explicitly re-check/re-run against AGENTS.md

In any case, I re-wrote the comment not to contain temporal language

Comment thread docs/disaggregation.md Outdated

**Conditional-decode 412 gate**

When this plugin is configured, it also drives the EPP's RFC 7240 `Prefer: if-available` (conditional-decode) gate. The coordinator uses that header to mark a speculative early-decode attempt: forward to a decode worker only if its KV cache already covers the prompt, otherwise return HTTP 412 Precondition Failed so the coordinator restarts the pipeline at encode/prefill/decode.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if this gate can be described independently, with coordinator use as a note.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-wrote the comments to describe the gate independently and pull the coordinator behavior into a note. Thank you!

// given the chosen decode endpoint, should the request be rejected with HTTP
// 412 (because the local KV cache does not cover enough of the prompt) or
// forwarded? At most one such plugin is consulted per director.
type ConditionalDecodeDecider interface {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description should be self-contained, and descriptive of what the module does.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-phrased the description.

Thank you!

Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
Signed-off-by: Dmitri Pikus <DPIKUS@il.ibm.com>
@github-actions

Copy link
Copy Markdown
Contributor

This PR is marked as stale after 21d of inactivity. After an additional 14d of inactivity (7d to become rotten, then 7d more), it will be closed. To prevent this PR from being closed, add a comment or remove the lifecycle/stale label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Categorizes issue or PR as related to a new feature. lifecycle/stale size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants